/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package naivebayes;

import TextManager.FileWordStream;
import TextManager.Vocabulary;
import TextManager.WordOccurrence;
import TextManager.WordOccurrencesMap;
import java.io.*;
import java.rmi.UnexpectedException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;

/**
 *
 * @author Jaime
 */
public class Model {
    
    public static final boolean DEBUG = false;
    
    private DocumentsGroup testGroup;
    private List<DocumentsGroup> learningGroups;
    private Vocabulary vocabulary;
    private double positivePropAPriory;
    private ConfusionMatrix confusionMatrix;    
    private boolean modelCalculated;
    private WordOccurrencesMap positiveOccurrences;
    private WordOccurrencesMap negativeOccurrences;
    
    public Model(DocumentsGroup testGroup, Vocabulary vocabulary)
    {
        this.testGroup = testGroup;
        this.vocabulary = vocabulary;
        this.learningGroups = new ArrayList<DocumentsGroup>();
        
        modelCalculated = false;
        positiveOccurrences = new WordOccurrencesMap(vocabulary);
        negativeOccurrences = new WordOccurrencesMap(vocabulary);
        
        confusionMatrix = new ConfusionMatrix();
    }
    
    public void addLearningGroup(DocumentsGroup learnGroup)
    {
        assert !modelCalculated : "Must not add groups if models is calculated!";
        
        learningGroups.add(learnGroup);
    }
    
    public int getNumberOfPositiveOccurrences(WordOccurrence word)
    {
        int occurrences = 0;
        
        for(DocumentsGroup group:learningGroups)
        {
            occurrences+=group.getNumberOfPositiveOccurrences(word);
        }
        return occurrences;
        
    }
    
    public int getNumberOfNegativeOccurrences(WordOccurrence word)
    {
        int occurrences = 0;
        
        for(DocumentsGroup group:learningGroups)
        {
            occurrences+=group.getNumberOfNegativeOccurrences(word);
        }
        return occurrences;
        
    }
    
    public int totalPositivePositions()
    {
        int total = 0;
        for(DocumentsGroup group:learningGroups)
        {
            //total += group.getNumberOfPositiveWords();
            total += group.getNumberOfPositiveOccurrences();
        }
        
        return total;
    }
    
    public int totalNegativePositions()
    {
        int total = 0;
        for(DocumentsGroup group:learningGroups)
        {
            total += group.getNumberOfNegativeOccurrences();
        }
        
        return total;
    }
    
    public void train()
    {
        
        assert !modelCalculated : "Must not calculate twice!";
        
        calculatePropAPriori();
        
       for(WordOccurrence word:vocabulary)
       {
           int positiveOccur = getNumberOfPositiveOccurrences(word);
           int negativeOccur = getNumberOfNegativeOccurrences(word);
           
           positiveOccurrences.addManyOccurrences(word, positiveOccur);
           negativeOccurrences.addManyOccurrences(word, negativeOccur);
           
       }
       
       modelCalculated = true;
    }
    
  
    
    public double calculatePositiveProb(WordOccurrence word)
    {
        
        int positiveOccur = positiveOccurrences.getNumberofOccurrences(word);
        
        double posProb = (positiveOccur*1.0 + 1)/(totalPositivePositions()+vocabulary.size());
        
        return posProb;
        
    }
    
    public double calculateNegativeProb(WordOccurrence word)
    {
        int negativeOccur = negativeOccurrences.getNumberofOccurrences(word);
        
        double negProb = (negativeOccur*1.0 + 1)/(totalNegativePositions()+vocabulary.size());
        
        return negProb;
        
    }
    
    public boolean classify(File file) throws FileNotFoundException, IOException
    {
        
        assert modelCalculated : "cannot classify if the model is not calculated";
        
        FileWordStream fws = new FileWordStream(file);
        
        double logProbPos = 0;
        double logProbNeg = 0;
        
        for(WordOccurrence w:fws)
        {
            logProbPos += Math.log(calculatePositiveProb(w));
            logProbNeg += Math.log(calculateNegativeProb(w));
            
        }
        
        
        
        logProbPos += Math.log(positivePropAPriory);
        logProbNeg += Math.log(1 - positivePropAPriory);
        
        
        return logProbPos >= logProbNeg;
        
    }
    
    public void performTest() throws FileNotFoundException, IOException
    {
        //Testing the positive files
        if(DEBUG)
        {
            System.out.println("Positive files' classification:");
        }
        for(File f:testGroup.getPositiveFiles())
        {
            boolean classifiedPositive = classify(f);
            
            if(DEBUG)
            {
                if(classifiedPositive)
                {
                    System.out.println("POSITIVE");
                }
                else
                {
                    System.out.println("NEGATIVE");
                }
            }
          
            getConfusionMatrix().newOccurrence(true, classifiedPositive);
            
        }
        
        //Testing the negative files
        if(DEBUG)
        {
            System.out.println("Negative files' classification:");
        }
        for(File f:testGroup.getNegativeFiles())
        {
            boolean classifiedPositive = classify(f);
            
            if(DEBUG)
            {
                if(classifiedPositive)
                {
                    System.out.println("POSITIVE");
                }
                else
                {
                    System.out.println("NEGATIVE");
                }
            }
            
            getConfusionMatrix().newOccurrence(false, classifiedPositive);
        }
        
        
    }
    
    public String analysisToString()
    {
        return getConfusionMatrix().toString();
    }
    
    public String toString()
    {
        String toString = "";
        
        //toString += "Vocabulary size:"+vocabulary.size()+"\n";
        
        toString += "Test group:\n"+testGroup.toString();
        
        toString += "Learning groups:\n";
        
        for(DocumentsGroup c:learningGroups)
        {
            toString += c.toString()+"\n";
        }
        
        return toString;
        
    }

    private void calculatePropAPriori() {
        
        double numFilesPos = 0;
        double numFilesNeg = 0;
        
        for(DocumentsGroup g:learningGroups)
        {
            numFilesPos += g.getPositiveFiles().size();
            numFilesNeg += g.getNegativeFiles().size();
        }
        
        positivePropAPriory = numFilesPos/(numFilesPos+numFilesNeg);
        
    }
    
    public void printProbabilitiesToFile(File file) throws IOException
    {
        FileWriter outFile = new FileWriter(file);
        PrintWriter out = new PrintWriter(outFile);
        
        out.println("<Word> : <positive probability> | <negative probability>");
        
        DecimalFormat df = new DecimalFormat("0.######");
        
        for(WordOccurrence word:vocabulary)
        {
            out.print(word.getContent()+" : ");
            out.print(df.format(calculatePositiveProb(word))+" | ");
            out.println(df.format(calculateNegativeProb(word)));
        }
        
        out.close();
        outFile.close();
        
    }

    /**
     * @return the confusionMatrix
     */
    public ConfusionMatrix getConfusionMatrix() {
        return confusionMatrix;
    }
    
}
